package com.qf.security1.filters;

import com.alibaba.fastjson.JSON;
import com.qf.security1.pojo.Code;
import com.qf.security1.pojo.R;
import com.qf.security1.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class VerifyTokenFilter extends BasicAuthenticationFilter {

    public VerifyTokenFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");

        //验证token的合法性
        String token = request.getParameter("token");
        if (StringUtils.isEmpty(token)){
            //尝试从头中获取
            token = request.getHeader("token");

            if (StringUtils.isEmpty(token)){
                //没登陆或者这个资源访问不需要登录 -- 放行
                chain.doFilter(request,response);
                return;
            }
        }

        /*
            因为token是有过期时间的，如果用户的token丢失了，在有效期内，不篡改的请情况下，是可以无限访问用户数据的
            一旦服务器发现这个token的方式是有异常情况，服务器应该如何频闭这个token访问操作？？？

            解决方案：黑名单
                将异常的token存入到redis中，解析具体的token之前，先判断这个token是否在redis的黑名单中，如果在，中断解析，提示重新登录即可
         */
        //假设这个是redis
        List<String> blackNames = new ArrayList<>();
        //模拟添加了一个黑名单
        blackNames.add("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ6aGFuZ3NhbiIsInJvbGVzIjpbIlJPTEVfUDEiLCJST0xFX1AyIl0sImV4cCI6MTY5MjI0OTM4MiwidXNlcm5hbWUiOiJ6aGFuZ3NhbiJ9.dWXWDXHK4hQZ6DruMr8kaACX5-RHkxzD1KGZyRXleHk");
        if (blackNames.contains(token)){
            R r = R.fail(Code.NOLOGIN,"token非法");
            response.getWriter().write(JSON.toJSONString(r));
            return;
        }

        //获取到了token,对token的合法性进行验证
        try {
            Claims claims = JwtUtils.parseBody(token);

            saveSecurityContext(claims);

            chain.doFilter(request,response);
        }catch (ExpiredJwtException expiredEx){
            //token过期了 todo:额外处理
            /*
                token是有2个小时的过期时间
                    用户第一次使用后，中间隔了两个小时再来访问 --- 提示token过期，重新登录
                    用户第一次使用后，中间隔了1小时58分钟来访问，持续使用了5分钟，使用过程中告诉用户说要重新登录
                        影响了用户使用体验？该如何解决呢？？？
                        解决方案：我允许你过期时间超出30分钟
                            过期时间在30分钟内，我发现过期了，会重新签发新的token

             */

            Claims claims = expiredEx.getClaims();
            //过期时间的毫秒值
            long expireLong = claims.getExpiration().getTime();
            long nowLong = System.currentTimeMillis();

            if (nowLong <= expireLong+1000*60*30){
                //允许复活 -- 重新签发token
                String refresh_token = JwtUtils.createToken(claims, claims.getSubject());
                //通过响应头，将新的token返回到浏览器
                response.setHeader("refresh_token",refresh_token);

                //设置security上下文信息 + 放行
                saveSecurityContext(claims);
                chain.doFilter(request,response);
            }else{
                R r = R.fail(Code.NOLOGIN,"token过期");
                response.getWriter().write(JSON.toJSONString(r));
            }
        }catch (MalformedJwtException | SignatureException | IllegalArgumentException e){
            //token非法
            R r = R.fail(Code.NOLOGIN,"token非法");
            response.getWriter().write(JSON.toJSONString(r));
        }
    }

    private void saveSecurityContext(Claims claims) {
        //获取用户名
        String username = claims.get("username", String.class);
        //获取用户权限
        List<String> roles = claims.get("roles", List.class);
        String[] strArray = roles.toArray(new String[0]);
        List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(strArray);

            /*
                一定要向security的上下文中存入用户的认证信息
                    参数一：用户名
                    参数二：密码（不给）
                    参数三：用户权限
             */
        UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(username,null,authorityList);
        SecurityContextHolder.getContext().setAuthentication(authResult);
    }
}
